package top.wardyan.sqlchecker.business;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import top.wardyan.base.common.db.po.SQLServerDataType;
import top.wardyan.base.common.util.DateUtil;

//import cn.huttool.core.io.FileUtil;

/**
 * SQL检查和自动格式化工具
 * @author harrydeep@wardyan.top
 * 2022年4月19日
 */
public class SqlChecker {

	//static final DBM dbm = DBM.getInstance();
	
	private static final String AUTO_PARAM_INIT_PREFIX = "\t-- 自动生成参数处理 ";
	
	public static final List<String> WRAPS = new ArrayList<>() {{
		this.add("select");
		this.add("from");
		this.add("inner");
		this.add("left");
		this.add("right");
		this.add("full");
		this.add("where");
		this.add("and");
		this.add("or");
		this.add("if");
		this.add("end");
		this.add("begin");
		this.add("go");
		this.add("group");
		this.add("order");
		this.add("else");
		this.add("union");
		this.add("set");
		this.add("create");
		this.add("alter");
		this.add("drop");
		this.add("declare");
		this.add("open");
		this.add("fetch");
		this.add("insert");
		this.add("delete");
		this.add("truncate");
		this.add("drop");
	}};
	
	
	protected static List<String> spliteToWords(String  line){
		List<String> statement = new ArrayList<>();
		boolean first = false;
		StringBuilder sb = new StringBuilder();	// 用来拼接当前的单词
		for(int j = 0, size = line.length(); j < size; j++) {
			//Console.log("**", line.charAt(j));;
			char ch = line.charAt(j);
			String c = String.valueOf(ch);
//			if(!first && (c < 32)) {		//  || c > 126
//				Console.log("控制字符,跳过:" , (int)c);
//				continue;
//			}
			//List<String> ss = ReUtil.findAll("([\\s,])+", c, 0);
			//Console.log("****", ss);
			// if(ss.size() > 0) {
			
			// 如果括号已经扩出去了就一定要等括号结束		// 怎么等? 万一括号里也有子查询?
			// 如果前面是ISNULL, 那么遇到逗号都不要停止		?? 
			

			if(ch == 9 || ch == 10 || ch == 11 || ch == 12 || ch == 13 || ch == 27 || ch == 32 || (ch == 44) || j == size - 1) {		//  || ch == 40
				// 找到空白字符或者逗号了
				// 如果是空白字符,则把前面的字符作为一个单词; 如果是逗号, 还要把逗号也作为一个单词
//				if(ch == 44) {
//					// 逗号
//				}else {
//					// 不是逗号, 就是空白字符了
//				}
				//  如果这是这一行最后一个字符了都没遇到空白字符, 就结束 (默认情况下java匹配不到换行符?)
				// 如果最后一个是标点符号则忽略
				if(j == size - 1 && (ch != 44)) {		//  && ch != 39	// 最后一个单引号为啥不要
					sb.append(ch);
				}
				
				// 对这个单词的处理:
				// 如果是关键字而且是小写, 就转换成大写
				// 如果是表名, 检查是否带dbo, 没带的话加dbo前缀
				// 如果这是表名, 检查是否有nolkock
				
				String word = sb.toString();
				String lowercase = word.toLowerCase();
				
				// 如果是像nvarchar(100)这种的, 也大写
				// 还有问题:datetime和int没有大写
				if(SQLServerKeywords.KEYWORDS.contains(lowercase)) {
					word = word.toUpperCase();
				}
				// 或者是varchar(100)这种的
				// 都有哪些?
				// 要包括引号的才
				if(lowercase.contains("(")) {
					
					//Console.log("++++++", lowercase, first);
					
					List<String> ss = ReUtil.findAll("char\\([0-9]*?\\)", lowercase, 0);
					if(ss.isEmpty()) {
						ss = ReUtil.findAll("char\\(max\\)", lowercase, 0);
					}
					if(ss.size() > 0) {
						//Console.log(ss);
						word = word.toUpperCase();
					}
				}
				// ISNULL也要大写
				if(lowercase.contains("isnull(")) {
//					List<String> ss = ReUtil.findAll("isnull\\(.*?\\)", lowercase, 0);
//					if(ss.size() > 0) {
//						word = word.toUpperCase();
//					}
					// word = word.toUpperCase();
					word = word.replace("isnull", "ISNULL");
				}
				
				// MAX, MIN,等函数
				// TODO
				
				// 如果是行首的制表符或者空格, 就不加
				// 或者这一行只有括号
				if(first || (lowercase.replace(" ", "").equals("("))) {
					statement.add(word);
				}
				
				sb = new StringBuilder();
				if(ch == 44) {
					statement.add(",");
				}

				// 重头戏: 判断是否需要换行
				// 还要判断: 如果当前已经在
				if(WRAPS.contains(lowercase)) {
					// 换行要加在前面	// 譬如遇到FROM, 是from就开始换行了而不是FROM后面换行
					// statement.add(System.lineSeparator());
					statement.add(0, System.lineSeparator());
				}
				
			}else {
				// 没找到空白字符或者逗号, 继续下一个字符
				// sb.append(c);
				sb.append(ch);
				first = true;
			}
			
		}
		return statement;
	}
	
	
	
	public static String check(String sql) {
		String rs = sql;
		// 循环检查每一个字符,遇到空白字符就算是完成一个单词了
		
		// sql = SqlChecker.dealTableNames(sql);
		
		// join(, 以及select(, from(, where(都在中间加一个空格
		rs = rs.replaceAll(" join\\(", " join (")
				.replaceAll("\\(select ", "( select ")
				.replaceAll("from\\(", "from (")
				.replaceAll("where\\(", "where (")
				.replaceAll("and\\(", " and (")
				.replaceAll("or\\(", " or (")
				.replaceAll("apply\\(", "apply (")
				;
		
		int i = 0;
		int indent = 0;		// 缩进
		List<String> lines = rs.lines().collect(Collectors.toList());
		var it = rs.lines().iterator();
		while(it.hasNext()) {
			var line = it.next();
			
			// 保存解析到的每个词. 注意, 标点符号也算一个!
			// 去掉了前置的制表符和空格
			// 遍历这一行的每一个字符
			
			
			
			if(line.length() > 0) {
				List<String> statement = spliteToWords(line);
				
				// 如果带有(,而且括号前的是关键字
				for(int p = 0, size = statement.size(); p < size; p++) {
					var w = statement.get(p);
					if(w.contains("(") && w.indexOf("(") > 1) {
						String left = w.substring(0, w.indexOf("("));
						if(SQLServerKeywords.KEYWORDS.contains(left)) {
							statement.set(p, left.toUpperCase() + w.substring(w.indexOf("(")));
						}
					}
					// 如果包括), 而且除了括号外其他是关键字,而且前一个单词是AS, 则是类似这种的: cast(null as datetime),
					w = statement.get(p);
					if(w.endsWith(")") && p > 1 && statement.get(p - 1).equalsIgnoreCase("as")) {
						String left = w.substring(0, w.length() - 1);
						if(SQLServerKeywords.KEYWORDS.contains(left)) {
							statement.set(p, left.toUpperCase() + " )");
						}
					}
				}
				
				String s = CollectionUtil.join(statement, " ");
				//Console.log("++++", i, statement, "----", line);
				lines.set(i, s);
				
				//Console.log("////", i, CollectionUtil.join(lines, " "));
				//lines.add(i, s);
				// Console.log(CollectionUtil.join(statement, ";"));
				
			}
			
			
			i++;
		}
		
//		rs = CollectionUtil.join(lines, System.lineSeparator());
//		Console.log(rs);
//		System.exit(0);
		
		
		
//		Console.log("********************************************");
//		// Console.log(lines.toArray());
//		for(var line : lines) {
//			Console.log(line);
//		}
		

		rs = CollectionUtil.join(lines, System.lineSeparator());
		

		rs = SqlChecker.dealTableNames(rs);
		
		rs = initParams(rs);
		lines = rs.lines().collect(Collectors.toList());
		
		
		// 删除前后的空格
		i = 0;
//		for(var line : lines) {
//			lines.add(i, line.strip());
//			i++;
//		}
		var v = lines.iterator();
		while(v.hasNext()) {
			var line = v.next();
			// 逗号前面的空格可以去掉
			// 去掉多余的空格
			String s = line.strip().replaceAll(" ,", ",");
			// 每个逗号后面都加一个空格
			s = s.replaceAll(",", ", ");
			s = s.replaceAll("([ ]{2,})", " ");
			lines.set(i, s);
			i++;
			
		}
		
		
		// 删除连续空行
		// 移除空行
		
		Pattern p = Pattern.compile("([\\n\\r]{4,})", Pattern.CASE_INSENSITIVE|Pattern.MULTILINE);
		
		rs = CollectionUtil.join(lines, System.lineSeparator());
		

		
		
//		List<String> ssss = ReUtil.findAll(p, rs, 0);
//		Console.log(ssss);
		rs = ReUtil.replaceAll(rs, p, System.lineSeparator());
		

		//rs = SqlChecker.dealTableNames(rs);
		
		lines = rs.lines().collect(Collectors.toList());
		
		
		
		
		var t = lines.iterator();
		
		
		

		// 如果from和join后面直接换行了,把括号放到下一行了, 就把括号放到上一行去
		i = -1;
		String temp = null;
		while(t.hasNext()) {
			var line = t.next();
			i++;
			
			if(line.toLowerCase().strip().startsWith("(")
					&& i > 0
					&& (
							(temp = lines.get(i - 1).toLowerCase().strip()).endsWith("from")
							|| temp.endsWith("join")
							|| temp.endsWith("where")
						)
					) {
				lines.set(i-1, lines.get(i-1) + " " + line);
				lines.set(i, "");
				// t.remove();
			}
			
		}
		
		t = lines.iterator();
		while(t.hasNext()) {
			var line = t.next();
			
//			List<String> resultFindAll = ReUtil.findAll(p, line, 0, new ArrayList<String>());
//			
//			if(resultFindAll.size() > 0) {
//				line = li
//			}
			
			
			if(line.strip() .length() < 1) {
			// if(line.strip().equals("")) {
				t.remove();
			}
			// 如果是union语句, 前后各加一行空行
			
			//Console.log(line);
		}
		
		
		
		
		// 重头戏来了: 缩进
		// 遇到以下关键字就要缩进 (什么时候恢复?)\
		/**
		关键字			恢复
		CREATE 			AS
		BEGIN			END		要配套的才行
		SELECT			FROM	字段分行的话
		**/
		
		
		// 如果脚本没有被AS BEGIN END包围, 就加一个
		t = lines.iterator();
		int ok = 0;
		i = -1;
		while(t.hasNext()) {
			i++;
			String line = t.next();
			String n = line.toLowerCase();
			
			// 要区分: 参数部分和后续部分
			if(ok ==0) {
				// 还没开始,遇到create或者alter开头才算开始
				if(n.startsWith("create") || n.startsWith("alter")) {
					 ok = 1;
				}
			}else if(ok == 1) {
				// 参数部分, 遇到单独一行AS或者SET NOCOUNT ON 了就转入正文部分
				if(n.startsWith("as") || n.startsWith("set nocount")) {
					ok = 2;
				}
			}else if(ok == 2) {
				// 如果正文的第一行不是BEGIN ... END
				if(!n.startsWith("begin")) {
					lines.add(i, "BEGIN");
					lines.add("END");
				}
				break;
			}
		}
		String lastline = lines.get(lines.size() - 1).toLowerCase();
		if(!lastline.equals("go")) {
			lines.add("GO");
			lines.add("");		
		}
		// 如果第一行不是空的,就加一个空行
		// 没有判断null?
		String firstline = lines.get(0).toLowerCase();
		if(firstline.trim().length() < 2) {
			lines.add(0, "");
		}
		
		// 先缩进再处理后面的对齐的问题: 譬如参数那里, 数据类型和注释对齐
		// 固定AS单独为一行了
		i = -1;
		t = lines.iterator();
		ok = 0;		// 0还没开始 1参数部分 2正文部分
		boolean firstParam = true;

		int nsize = 25;		// 参数名默认长度15
		
		int dsize = 15;
		
		indent = 0;
		boolean whereindent = false;	// where的条件缩进
		boolean fromindent = false;
		while(t.hasNext()) {
			i++;
			String line = t.next();
			String n = line.toLowerCase();
			
			
			
			
			// 自动加空行
			
			// 要区分: 参数部分和后续部分
			if(ok ==0) {
				// 还没开始,遇到create或者alter开头才算开始
				
				if(n.startsWith("create") || n.startsWith("alter")) {
					 ok = 1;
				}
			}else if(ok == 1) {
				// 不 也可能是以逗号开头的 // ***
				// 如果是以逗号开头的就
				
				
				if(n.startsWith(",")) {
					
				}
				if(n.startsWith("@")) {	// 如果是以@开头,就是参数来的
					// 拆成三部分: 参数名, 数据类型, 注释(可能没有)
					
					int pre = n.indexOf(" ");
					int next = n.indexOf(" ", pre + 1);
					
					String name = n.substring(0, pre).trim();
					//next += pre;
					String datatype = n.substring( pre, next > 0 ? next : n.length()).trim();
					// datatype最后面的逗号去掉
					if(datatype.endsWith(",")) {
						datatype = datatype.substring(0, datatype.length() - 1);
					}
					String note = "";
					if(n.contains("--")) {	// 有注释
						note = n.substring( n.indexOf("--")).trim();
					}
					//Console.log(pre,next, line, name, "**", datatype, "**", note);
//					if(name.length() + 5 >= nsize) {
//						nsize = name.length() + 5;
//					}
//
//					if(datatype.length() + 5 >= dsize) {
//						dsize = datatype.length() + 5;
//					}
					
					// line = String.format((firstParam ? "" : ", ") + "%-" + nsize + "s\t" + (firstParam ? "" : "\t") + "%-" + dsize + "s\t%s", name, datatype, note);
					line = String.format((firstParam ? "" : ", ") + "%-" + (nsize + (firstParam ? 2 : 0)) + "s\t" + "%-" + dsize + "s\t%s", name, datatype.toUpperCase(), note);
					line = "\t" + line;
					//Console.log(line);
				}
				// 参数部分, 遇到单独一行AS或者SET NOCOUNT ON 了就转入正文部分
				if(n.startsWith("as") || n.startsWith("set nocount") || n.startsWith("begin")) {
					ok = 2;
				}
				
				firstParam = false;
			}else if(ok == 2) {
				// 正文了
				// 遇到要加缩进的: begin
				if(n.startsWith("end") || n.startsWith(") as")) {
					indent--;
				}else if(whereindent && (!n.startsWith("and") && !n.startsWith("or") && !n.startsWith("--"))) {		// 注释不管
					indent--;
					whereindent = false;
					// Console.log("*****************", n);
				}else if(fromindent && (n.startsWith("from") || n.startsWith("where") || n.startsWith("inner join") || n.startsWith("left join"))) {
					indent--;
					fromindent = false;
				}
				
				line = StrUtil.repeat("\t", indent) + line;
				
				// from/join后面带括号的也换行,括号可能在下一行
				if(n.startsWith("begin")) {
					indent++;
				}else if(n.startsWith("from(") 
						|| n.startsWith("from (") 
						|| n.startsWith("inner join(") 
						|| n.startsWith("inner join (")
						|| n.startsWith("left join(") 
						|| n.startsWith("left join (")
						|| (
								(n.startsWith("from") || n.startsWith("inner join") || n.startsWith("left join"))
								&& lines.size() > i + 1
								&& lines.get(i+1).toLowerCase().startsWith("(")
							)
						//	|| n.startsWith("")
						) {
					indent++;
				}
				// where后面的行, 都缩进一个tab,直到遇到不是以and或者or开头的
				else if(n.startsWith("where")) {
					indent++;
					whereindent = true;
					// .log("*****************", n, lines.get(i + 1));
					// line = "" + line;
				}
				// select后面的行, 都缩进一个tab,直到遇到from 或者 where
				// 如果from和where都在同一行就不缩进
				else if(n.startsWith("select") && (!n.contains(" from") && !n.contains(" where"))) {
					indent++;
					fromindent = true;
					// .log("*****************", n, lines.get(i + 1));
					// line = "" + line;
				}
				// if下一行如果没有BEGIN就缩进? 这个不好搞: 暂时不好判断if的分支判断语句和执行语句是否在同一行
				
				
				// 如果是union,前后各加一个空行
				if(n.startsWith("union")) {
					line = System.lineSeparator() + line;		//  + System.lineSeparator()
				}
				
				// 遇到SELECT和where,一律加一个空行
				if(n.startsWith("select") ) {		// || n.startsWith("where")
					line = System.lineSeparator() + line;
				}
				// 遇到子查询,结尾也加个空行
				if(n.startsWith(") as")) {
					line = System.lineSeparator() + line;
				}
				// 遇到set nocount,前后各加空行
				if(n.startsWith("set nocount")) {
					line = System.lineSeparator() + line + System.lineSeparator();
				}
				
				// 遇到类似这种注释, 前面加多个空行: -- -- (连续两个注释)

				if(n.startsWith("-- --") || n.startsWith("-- - --")) {
					line = System.lineSeparator() + line;
				}
				
				// 
			}
			
			lines.set(i, line);
		}
		
		// 最后一行如果不是GO, 则加上一个
		
		// 如果不是[数值] (中括号内括着数值), 就把中括号去掉 
		// 当然这可能会导致关键字的问题, 但我们不鼓励用关键字, 因此用了关键字就尽管报错吧
		
		rs = CollectionUtil.join(lines, System.lineSeparator());
		
		// rs = SqlChecker.dealTableNames(rs);
		
		//rs = ReUtil.replaceAll(rs, "min(", "");
//		rs = rs.replaceAll("min(", "")
//				.replaceAll("max(", "")
//				.replaceAll("sum(", "")
//				.replaceAll("avg(", "")
//				.replaceAll("floor(", "")
//				.replaceAll("ceil(", "");
		
		// 括号后和前加空格
		
		Console.log(rs);
		// Console.log(SqlChecker.dealTableNames(rs));
		return rs;
	}
	
	
	
	
	
	// 格式化对齐
	
	public static String dealTableNames(String sql) {
		String rs = sql;
		
//		var tablerecords = Db.find("select name from sys.tables");
//		
//		// 脚本内出现过的表名
//		var ts = tablerecords.stream().map(new Function<Record,String>(){
//			@Override
//			public String apply(Record t) {
//				return t.getStr("name");
//			}
//		}).filter(new Predicate<>() {
//			@Override
//			public boolean test(String t) {
//				return sql.contains(t);
//			}
//		}).collect(Collectors.toList());
		
		// Console.log(ts.size());
		
		var lines = sql.lines().collect(Collectors.toList());
		
		var it = sql.lines().iterator();
		
		// 脚本里每一行, 都拆分成每一个词, 然后遍历词
		// 如果遇到单行注释, 就直接下一行
		var no = -1;	// 行序号 以便把改过的内容回写
		boolean edit = false;	// 有没有改过
		//String[] words = null;
		List<String> words = new ArrayList<>();
		boolean lastone = true;	// 最后再跑一次
		while(it.hasNext()) {
			no++;
			
			var line = it.next();
			
			words = new ArrayList<>();;
			if(line.length() > 0 && (words = spliteToWords(line)).size() > 0) {		// 空行直接跳过  
				// var words = line.split("[\\s,]");
				//Console.log(words);
				// 遍历数组里的每一个词
				var idx = 0;
				var size = words.size();
				
			
				while(idx < size) {
//					if(!it.hasNext()) {
//						Console.log(idx,size);
//					}
					// for(var w : words) {
					String name = null;		// 用来临时存遍历到的表名
					for(;idx < size;) {
						name = null;
						String w = words.get(idx);
						idx++;
						// 表名的开始: 可能以以下开始:	// 暂不支持表名在最前面的情况, 譬如: from在上一行, 表名在下一行开头
						// from, join
						// 表名的结束(表名的后面): where, as. \s (表名后面不可能直接接on,因为要别名)  [或者没写as,直接别名:暂不支持这种情况]
						if(idx >= size) {	// 如果递增完了已经超过数组长度了直接就退出, 不检查是否表名了
							break;
						}
						if(w.equalsIgnoreCase(FROM) || w.equalsIgnoreCase(JOIN)) {		// 接下来是表名了
							name = words.get(idx);
							idx++;
							break;
						}
					}
					if(name != null) {		// 找到表名了
						//Console.log("找到疑似表名:", name);
						// 检查是否真正的表名 (from或者join后面也可能接子查询或者临时表或者表变量)
						// 如果以#临时表或者@表变量开头,直接跳过	## 以后再改正则
						if(name.startsWith("#") || name.startsWith("@") || name.startsWith("(")) {
							continue;
						}
						// 如果以dbo.开头,就去掉dbo.
						else if(name.toLowerCase().startsWith("dbo.")) {
							name = name.substring(4);
						}
						// 不 没有dbo开头就加上dbo // 等下一起加
						
						// 在这里不会
						//Console.log("找到表名:", name, words[idx], words[idx+1]);
						// 检查有没有别名
						// 暂不支持这种写法, 以后再考虑: left join kt_device (nolock) as k
						// 要检查一下当前语句 是否delete from kt_log这种格式的
						if(idx > 1 && words.get(idx-2).equalsIgnoreCase("delete")) {		// 这一个待测试
							continue;
						}
						if(idx > 0 && words.get(idx).equalsIgnoreCase("where")) {
							// 没有as, 没有where
							words.set(idx-1, words.get(idx-1) + " (nolock) ");
							edit = true;
							//continue;
						}
						if(words.get(idx).equalsIgnoreCase("(nolock)")) {
							// 没有as 这种的不用加什么
							continue;
						}
						if(words.get(idx).toLowerCase().contains("(nolock)")) {
							// with (nolock)也不用管
							continue;
						}
						
						if(words.get(idx).equalsIgnoreCase("as") && idx + 1 < size)  {
							String alias = words.get(idx+1);	// 别名
							// 是否有nolock?
							if(idx + 2 < size) {
								if(words.get(idx+2).equalsIgnoreCase("(nolock)")) {
									// 有nolock, 啥都不用管
								}else {
									// as 别名 后 没有nolock
									// words[idx+1] += " (nolock)";
									words.set(idx + 1, words.get(idx + 1) + " (nolock)");
									edit = true;
								}
							}else {
								// as 别名后直接就换行了, 也加nolock
								// words[idx+1] += " (nolock)";
								words.set(idx + 1, words.get(idx + 1) + " (nolock)");
								edit = true;
							}
						}
						// 前面统一加dbo.
						name = "dbo." + name;
					}
					//Console.log(words);

//					if(!it.hasNext()) {
//						Console.log(words);
//					}
				}
				// Console.log(words);
			}
			lines.set(no, CollectionUtil.join(words, " "));
		}
		

		
		rs = StrUtil.join(System.lineSeparator(), lines);
		
		//rs = rs.replaceAll("\\t", "");
		
		//Console.log(rs);
		
		// 格式化
		// SQLUtils.class
		// SQLUtils.format(rs, 0);
		
		// rs = SQLUtils.formatSQLServer(rs);
		//rs = SQLFormat.format(rs);
		//rs = SQLUtils.toSQLServerString(null)
		// SQLObject j = new SQLObject();
		// SQLUtils.toSQLServerString(null)
		//Console.log("*******************************************************************************************");
		//Console.log(rs);
		
		return rs;
	}

	public static final String FROM = "from";
	public static final String JOIN = "join";
	// public static final String JOIN = "";
	
	
	/**
	 * 自动处理参数:默认值
	 * 当然,要先检查有没有设置默认值了, 没有设置的话才需要
	 * 如果遇到了set nocount on, 那下一行就开始初始化
	 * 遇到if, select,set,关键字开始就是使用了, 要放在这些关键字的前面进行初始化  // 不 强制要求set nocount on
	 * 
	 * @param lines
	 */
	public static String initParams(String text) {
		
		// 如果已经初始化过了就不出来
		if(!text.contains(AUTO_PARAM_INIT_PREFIX)){
			List<String> lines = text.lines().collect(Collectors.toList());
			
			var params = params(lines);
			// int left = 0;	// set nocount距离左边有多少个字符
			if(params.size() > 0) {
				// 统一在set nocount 后
				int i = 0;
				for(var n : lines) {
					i++;
					if(n.contains(" nocount ") || n.contains(" NOCOUNT ")) {
						break;
					}
				}
				// Console.log(i);
				// 还要加个判断;未到文末
				if(i > 0) {
					lines.add(i, "\t");
					i++;
					lines.add(i, AUTO_PARAM_INIT_PREFIX + DateUtil.formatNow() );
					for(var p : params.entrySet()) {
						i++;
						// 初始化内容:
						// int, bigint, float, real, decimal,money,smallmoney,bit,tinyint 统一初始化为0
						// varchar,char,nvarchar,nchar,text统一初始化为""
						// datetime,date,time和smalldatetime不管
						String key = p.getKey();
						String value = "0";
						Integer datatype = p.getValue();
						
	//					if(StrUtil.containsIgnoreCase(key, "int")
	//							|| StrUtil.containsIgnoreCase(key, "bigint")
	//							|| StrUtil.containsIgnoreCase(key, "float")
	//							|| StrUtil.containsIgnoreCase(key, "real")
	//							|| StrUtil.containsIgnoreCase(key, "decimal")
	//							|| StrUtil.containsIgnoreCase(key, "money")
	//							|| StrUtil.containsIgnoreCase(key, "bit")
	//							) {
	//						value = "0";
	//					}else {
	//						value = "''";
	//					}
						if(datatype < 11 || datatype.equals(31)) {
							value = "0";
						}else if(datatype < 21) {
							value = "''";
						}else if(datatype < 31) {
							break;	// 
						}
						
							
							
						String str = String.format("\tSET %-20s = ISNULL(%-20s, %-2s)", key, key, value);
						lines.add(i, str);
					}
	
					i++;
					lines.add(i, "\t\r\n");
				}
			}
			var s = CollectionUtil.join(lines, System.lineSeparator());
			//Console.log(s);
			return s;
		}
		return text;
	}
	
	/**
	 * 读取存储过程脚本里的参数
	 * @param lines
	 * @return
	 */
	public static Map<String, Integer> params(List<String> lines) {
		Map<String, Integer> rs = new HashMap<>();
		
		// 从create或者alter开头, 截取到AS
		// 0未开始, 1参数区间内, 2完成(过了参数区间)
		int ok = 0;
		
		for(var s : lines) {
			//Console.log(ok, s);
			if(ok == 0) {
				// 还没开始, 检测到create / alter再开始
				if(s.contains("create") || s.contains("alter") || s.contains("CREATE") || s.contains("ALTER")) {
					// String re = "\\s(create|alter|CREATE|ALTER)\\s(proc|PROC|procedure|PROCEDURE)\\s";
					String re = "(create|alter|CREATE|ALTER)[\\s]+(?:proc|PROC|procedure|PROCEDURE)[\\s]+";
					//Console.log(s);
					//Console.log(re);
					List<String> resultFindAll = ReUtil.findAll(re, s, 0, new ArrayList<String>());
					System.out.println(resultFindAll);
					if(resultFindAll.size() > 0) {
						ok = 1;
					}
				}
			}else if(ok == 1) {
				// 已经开始了, 检查到AS后就要停止
				if(s.contains("as") || s.contains("AS")) {
					// AS 都是单独一行
					if(s.equalsIgnoreCase("as")) {
						ok = 2;
					}
				} 
				if(s.contains(" nocount ")  || s.contains(" NOCOUNT ") ) {
					ok = 2;
				}
			}
			
			// 如果仍然是1, 则很可能是参数行	// 要求一行一个参数
			// 去掉前置的空格和换行符,多个空格合并成一个空格
			// 参数以@开头
			// 去掉注释
			if(ok == 1) {
				if(s.contains("@")) {
					// 去掉前置的空白
					String str = s.substring(s.indexOf("@"));
					// 去掉多余的制表符和空格
					str = str.replaceAll("\\t", " ").replaceAll("[\\s]+", " ");
					// 去掉注释
					if(str.contains("--")) {
						str = str.substring(0, str.indexOf("--"));
					}
					// 如果以空格结尾, 则去掉	// 如果以空格开头, 则去掉
					str = str.trim();
					// 如果以逗号结尾,则去掉	// 如果以逗号开头,则去掉
					if(str.startsWith(",")) {
						str = str.substring(1);
					}
					if(str.endsWith(",")) {
						str = str.substring(0, str.length() - 1);
					}
					
					//Console.log(str);
					
					// 拆分为参数mignon和数据类型
					if(str.contains(" ")) {
						//Console.log(str.substring(0, str.indexOf(" ")));
						//Console.log(str.substring(str.indexOf(" ")));
						
						String p = str.substring(0, str.indexOf(" "));
						String datatype = str.substring(str.indexOf(" ") + 1);
						if(datatype.contains("(")) {	// varchar(100)这种
							datatype = datatype.substring(0, datatype.indexOf("("));
						}
						// Console.log(p, datatype.toLowerCase(),SQLServerDataType.DATATYPES.get(datatype.toLowerCase()));
						rs.put(p, SQLServerDataType.DATATYPES.get(datatype.toLowerCase().trim()));
					}
				}
			}
			
			if(ok == 2) {
				break;
			}
		}
		
		return rs;
		
	}
	
	
	public static void ttt() {
		String str = """
	,mc.mID as rSampl_ID, b.xBC,b.xCost ,b.xCost * (b.xQty - (isnull(o.xOutQty,0) - isnull(o.xInQty,0))) as xCostTotal
	from dbo.et_spl_in_bc as b
	left join dbo.et_spl_in as i on i.sID = b.mID
	left join et_style_mcolor as mc on b.rMColor_ID = mc.sID
	left join dbo.zt_depot as d on d.sID = i.rDepot_ID
	left join dbo.zt_depot_place as p on p.dID = i.rPlace_ID
	""";
		dealTableNames(str);
	}
	
	
	
	
	
	public static void test() {
		String filename = "03.alter ep_qryspl_stock.sql";
		
		var ls = FileUtil.readLines(filename, Charset.forName("gb2312"));
		
		var fs = FileUtil.readString(filename, Charset.forName("gb2312"));
		
		// System.out.println(fs);
		
		//SqlUtil.class
		//SqlUtil.
		//var f = SQLUtils.format(fs, "sqlserver");
		
		//System.out.println(f);
		
		//List<SQLStatement> statementList = SQLUtils.parseStatements(fs, JdbcConstants.SQL_SERVER);
		
		
		
		
		
		
		// System.out.println(tablenames(fs));
		
		
		// System.out.println(params(ls));;
		// initParams(fs);
		
		//var s = dealTableNames(fs);
		// Console.log(s);
		//s = format(s);
		
		//Console.log(s);
		// Console.log(fs);
		// System.exit(0);
		var s = check(fs);
		//Console.log(s);
	
	}
	
	

	public static void main(String[] args) {
		// System.out.println("hello world");
		test();
		//ttt();
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	 
	
	
	
	
	
	
	
	
	
	
	
	
	
	
}
